﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data.SqlClient;
using System.Xml;
using log4net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Data;

namespace Browis.Package
{
    public abstract class PackageHandler
    {
        public class CommandParameter
        {
            public string ID { get; set; }
            public string Type { get; set; }
            public string RefCommand { get; set; }
            public string RefColumn { get; set; }
            public string RefOrderInfoName { get; set; }
        }
        /// <summary>
        /// 包状态
        /// </summary>
        public enum PackageState
        {
            /// <summary>
            /// 空包
            /// </summary>
            None,
            /// <summary>
            /// 初始化
            /// </summary>
            Init,
            /// <summary>
            /// 创建命令节
            /// 创建完毕后才可对包进行客户端命令和数据命令的添加
            /// </summary>
            CreatCommand,
            /// <summary>
            /// 获取客户端数据命令
            /// </summary>
            GetUserTargetCommand,
            /// <summary>
            /// 获取数据命令
            /// 等待获取数据
            /// </summary>
            GetDataTarget,
            /// <summary>
            /// 获取数据完毕
            /// </summary>
            GetData,
            /// <summary>
            /// 添加数据结束
            /// </summary>
            CommandComplete,
            /// <summary>
            /// 包体整体处理完成
            /// </summary>
            Complete,
        }

        #region 属性
        // 日志
        protected ILog _log = null;

        //整体数据包流
        protected Stream _pakStream = null;
        //命令包流
        protected SectionStream _cmdStream = null;
        //数据流
        protected Stream _dataStream = null;

        //保留Command固定数据（命令、参数）写入的位置。
        protected long lPos = -1;
        //记命令节头最后的位置
        protected long lPosHeader = -1;
        /// <summary>
        /// 缓冲区大小    
        /// </summary>
        protected const int BUFFERSIZE = 10240;

        //当有超限数据时，保留超限Command的固定数据。
        public byte[] bufferForCmd = null;
        //当有超限数据时，保留超限Command的Data节的节头数据
        public byte[] bufferForCmdDataHeader = null;

        public DataPackage DataPackageMan { get; set; }
        /// <summary>
        /// 日志
        /// </summary>
        public ILog LogMan { get; set; }
        /// <summary>
        /// 包大小
        /// </summary>
        public int MaxSizeOfPackage { get; set; }
        /// <summary>
        /// 数据包名称
        /// </summary>
        public string StreamName
        {
            get
            {
                if (_pakStream != null && _pakStream.GetType() == typeof(FileStream) && ((FileStream)_pakStream).Name != null)
                {
                    return ((FileStream)_pakStream).Name;
                }
                else
                {
                    return string.Empty;
                }
            }
        }
        /// <summary>
        /// 流类型
        /// </summary>
        public Type StreamType
        {
            get
            {
                if (_pakStream != null) { return _pakStream.GetType(); }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// 数据包流
        /// </summary>
        public Stream PakStream
        {
            get { return this._pakStream; }
            set { this._pakStream = value; }
        }

        /// <summary>
        /// 保存分包后临时数据保存的文件名集合，没有分包时为null
        /// </summary>
        public List<string> LstSplitPakFiles { get; set; }

        public PackageState PakState { get; set; }

        /// <summary>
        /// 判断是否服务停止
        /// </summary>
        /// <returns></returns>
        public delegate bool IsStopPending();
        #endregion

        /// <summary>
        /// 创建打包对象
        /// </summary>
        /// <param name="streamType">数据流类型：FileStream/MemoryStream</param>
        /// <param name="dBName">数据库名称</param>
        /// <param name="cancelTransaction">是否事物</param>
        /// <param name="maxSizeOfPackage">包的最大容量</param>
        /// <param name="log">日志</param>
        public PackageHandler(string streamType, string dBName, bool cancelTransaction, int maxSizeOfPackage, ILog log)
        {
        }

        #region 打包命令
        /// <summary>
        /// 添加客户端运行的命令包
        /// 注意：数据包必须创建命令节
        /// </summary>
        /// <param name="ndTypeValue">XML:Type属性值 用法:nd.Attributes["Type"]</param>
        /// <param name="ndText">XML：Text 用法:TargetTextReplace(nd.SelectSingleNode("Text").FirstChild.Value, nd.SelectSingleNode("ReplaceParameters"));</param>
        /// <param name="ndReserveText">XML：ReserveText 用法:TargetTextReplace(nd.SelectSingleNode("ReserveText").FirstChild.Value, nd.SelectSingleNode("ReplaceParameters"));</param>
        public abstract void AddTargetCommands(string ndTypeValue, string ndText, string ndReserveText);

        /// <summary>
        /// 添加带参数的数据包
        /// 保留Command固定数据（命令、参数）写入的位置
        /// 注意：数据包必须创建命令节
        /// </summary>
        /// <param name="ndTypeValue">XML:Type属性值 用法:nd.Attributes["Type"]</param>
        /// <param name="ndText">XML：Text 用法:TargetTextReplace(nd.SelectSingleNode("Text").FirstChild.Value, nd.SelectSingleNode("ReplaceParameters"));</param>
        /// <param name="ndReserveText">XML：ReserveText 用法:TargetTextReplace(nd.SelectSingleNode("ReserveText").FirstChild.Value, nd.SelectSingleNode("ReplaceParameters"));</param>
        /// <param name="lstInfo">参数值集合</param>
        /// <param name="lstValue">命令值集合</param>
        public abstract void AddTargetCommands(string ndTypeValue, string ndText, string ndReserveText, List<ParameterInfo> lstInfo, List<CommandValue> lstValue);
        /// <summary>
        /// 关闭流 删除文件
        /// </summary>
        public abstract void DeleteStreamFile();
        /// <summary>
        /// 清除切割文件
        /// </summary>
        public abstract void ClearSpliteFiles();

        /// <summary>
        /// 关闭数据流
        /// </summary>
        /// <returns></returns>
        public bool CloseDataStream()
        {
            //如果向数据节中填写了值，关闭节流并将返回值置true
            if (_dataStream != null)
            {
                _dataStream.Close();
                _dataStream = null;
                return true;
            }

            return false;
        }
        /// <summary>
        /// 完成包
        /// 关闭包
        /// Complete
        /// </summary>
        public void ClosePack()
        {
            this.DataPackageMan.Close();

            this.PakState = PackageState.Complete;
        }

        /// <summary>
        /// 为数据包创建命令节
        /// 注意：使用后必须关闭。
        /// 例：
        /// using (pakHandler.CreatCommand(ndCmd.Attributes["ID"].Value, 0))
        /// {
        ///     添加命令处理逻辑...
        /// }
        /// </summary>
        /// <param name="name">命令名称</param>
        /// <param name="options">操作类型</param>
        /// <returns>命令节的流</returns>
        public abstract SectionStream CreatCommand(string name, UInt32 options);
        /// <summary>
        /// 关闭命令流
        /// </summary>
        /// <returns></returns>
        public abstract bool CloseCommandStream();
        /// <summary>
        /// 写入数据
        /// 注意：数据包必须添加写入数据命令
        /// </summary>
        /// <param name="cmd">sql命令对象</param>
        /// <param name="lstValue">参数集合</param>
        /// <param name="isStopPending">是否服务停止的方法</param>
        /// <returns>临时文件列表</returns>
        public abstract List<string> WriteData(SqlCommand cmd, List<CommandValue> lstValue, IsStopPending isStopPending);

        #endregion
    }
    /// <summary>
    /// 处理包对象
    /// </summary>
    public class PackageStreamHandler : PackageHandler
    {

        public PackageStreamHandler(string streamType, string dBName, bool cancelTransaction, int maxSizeOfPackage, ILog log)
            : base(streamType, dBName, cancelTransaction, maxSizeOfPackage, log)
        {
            PakState = PackageState.Init;
            //开始生成包文件，并把数据打入包中
            this.PakStream = GetEmptyStream(streamType);
            DataPackageMan = DataPackage.Create(this.PakStream);
            DataPackageMan.DBName = dBName;
            LogMan = log;
            DataPackageMan.CancelTransaction = cancelTransaction;
            MaxSizeOfPackage = maxSizeOfPackage;

        }

        #region 控制流

        //获取空的流
        private Stream GetEmptyStream(string sType)
        {
            Stream rtn = null;

            //lock (dicChannelMap)
            //{
            switch (sType)
            {
                case "FileStream":
                    for (int i = 0; i < 100; i++)
                    {
                        try
                        {

                            rtn = new FileStream(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DateTime.Now.AddMilliseconds(10 * i).ToString("yyyyMMddHHmmssff") + ".tmp"),
                                FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); 
                                break;
                        }
                        catch
                        {
                            if (i == 100)
                            {
                                throw;
                            }
                        }
                    }
                    break;
                case "PakTmpFile":
                    for (int i = 0; i < 100; i++)
                    {
                        try
                        {
                            rtn = new FileStream(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DateTime.Now.AddMilliseconds(10 * i).ToString("yyyyMMddHHmmssff") + ".pakTmp"), FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
                            break;
                        }
                        catch
                        {
                            if (i == 100)
                            {
                                throw;
                            }
                        }
                    }
                    break;
                case "MemoryStream":
                    rtn = new MemoryStream();
                    break;
            }
            //}
            return rtn;
        }

        // 将文本信息写入流
        /// <summary>
        /// 将文本信息写入流
        /// </summary>
        /// <param name="stream">流实例</param>
        /// <param name="sString">待写入字符串</param>
        private void WriteString(Stream stream, String sString)
        {
            Byte[] buffer = System.Text.Encoding.GetEncoding("gb2312").GetBytes(sString);

            stream.Write(BitConverter.GetBytes(buffer.Length), 0, sizeof(Int32));
            stream.Write(buffer, 0, buffer.Length);
        }

        // 创建数据节（Data）并创建内嵌头节，然后返回该节流
        /// <summary>
        /// 创建数据节（Data）并创建内嵌头节，然后返回该节流
        /// </summary>
        /// <param name="cmdStream">命令节流</param>
        /// <param name="lstValue">输出值信息集合</param>
        /// <returns>创建的数据节（Data）</returns>
        private SectionStream CreateDataSec(List<CommandValue> lstValue)
        {
            BinaryFormatter bf = new BinaryFormatter();
            SectionStream scStream = SectionStream.Create(_cmdStream, "Data", 0, LogMan);
            using (SectionStream nestedStream = SectionStream.Create(scStream, "DataHeader", 0, LogMan))
            {
                bf.Serialize(nestedStream, lstValue);
                nestedStream.Close();
            }
            return scStream;
        }
        // 根据sql命令获得的值，向Data节中添加数据
        /// <summary>
        /// 根据sql命令获得的值，向Data节中添加数据
        /// </summary>
        /// <param name="cmd">SQL命令</param>
        /// <param name="lstValue">输出值信息集合</param>
        /// <returns>如果获取数据总量超过阙值，将数据保存在文件中的文件名列表</returns>
        private List<string> WriteDataInDataSection(SqlCommand cmd, List<CommandValue> lstValue, IsStopPending isStopPending)
        {
            List<string> lstFile = new List<string>();
            Stream curStream = null;

            //需要处理内存流！！！
            //检查流数据量是否大于指定的数据量，大于时写入新的文件流中。
            if (_dataStream.Length > MaxSizeOfPackage * 1024 * 1024)
            {
                curStream = GetEmptyStream("PakTmpFile");
                lstFile.Add(((FileStream)curStream).Name);
            }

            using (SqlDataReader dr = cmd.ExecuteReader())
            {
                while (dr.Read())
                {
                    if (isStopPending())
                    {
                        if (curStream != null)
                        {
                            //检查流数据量是否大于指定的数据量，大于时写入新的文件流中。
                            if (curStream.Length > MaxSizeOfPackage * 1024 * 1024)
                            {
                                curStream.Flush();
                                curStream.Close();
                                curStream = GetEmptyStream("PakTmpFile");
                                lstFile.Add(((FileStream)curStream).Name);
                            }
                            WriteObjValue(curStream, dr, lstValue);
                        }
                        else
                        {
                            WriteObjValue(_dataStream, dr, lstValue);
                            //检查流数据量是否大于指定的数据量，大于时写入新的文件流中。
                            if (_dataStream.Length > MaxSizeOfPackage * 1024 * 1024)
                            {
                                curStream = GetEmptyStream("PakTmpFile");
                                lstFile.Add(((FileStream)curStream).Name);
                            }
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
            //当前文件流不为空，关闭文件流
            if (curStream != null)
            {
                //如果当前文件流没有写入数据，删除该文件记录
                if (curStream.Length == 0)
                {
                    lstFile.RemoveAt(lstFile.Count - 1);
                    curStream.Close();
                    if (curStream.GetType() == typeof(FileStream))
                    {
                        File.Delete(((FileStream)curStream).Name);
                    }
                }
                else
                {
                    curStream.Flush();
                    curStream.Close();
                }
            }
            if (lstFile.Count > 0)
            {
                return lstFile;
            }
            else
            {
                return null;
            }
        }


        //根据值类型的指示，读取每列的值，转换为字节数组保存在
        private long WriteObjValue(Stream curStream, SqlDataReader dr, List<CommandValue> lstValue)
        {
            int iCount = 0;
            //byte[] vleBuffer = null;
            byte[] buffer = new byte[BUFFERSIZE];
            DataReadStream drStream = null;

            long len = curStream.Position;

            //循环将dr中的数据值写入流中
            for (int i = 0; i < lstValue.Count; i++)
            {
                try
                {
                    //检查每个列是否允许空。如果允许，将该域是否空值信息写入流
                    if (lstValue[i].AllowNull)
                    {
                        if (dr.IsDBNull(i))
                        {
                            curStream.Write(BitConverter.GetBytes(true), 0, 1);
                            //为空值，跳过下面的运行。读写下一列
                            continue;
                        }
                        else
                        {
                            curStream.Write(BitConverter.GetBytes(false), 0, 1);
                        }
                    }
                    //循环读取各个列，并把数据写入流中
                    using (drStream = new DataReadStream(dr, i, lstValue[i].Type))
                    {
                        //为字符串和byte数组类型，且不设定长度，要先写入长度信息
                        if ((lstValue[i].Type.ToLower() == "string" || lstValue[i].Type.ToLower() == "byte[]") && lstValue[i].Length == 0)
                        {
                            curStream.Write(BitConverter.GetBytes(uint.Parse(drStream.Length.ToString())), 0, sizeof(uint));
                        }
                        //将数值写入流中
                        iCount = drStream.Read(buffer, 0, BUFFERSIZE);
                        while (iCount == BUFFERSIZE)
                        {
                            curStream.Write(buffer, 0, BUFFERSIZE);
                            iCount = drStream.Read(buffer, 0, BUFFERSIZE);
                        }
                        curStream.Write(buffer, 0, iCount);

                        drStream.Close();
                    }
                }
                catch
                {
                    LogMan.ErrorFormat("运行打包命令出现异常。");
                    LogMan.DebugFormat("运行打包命令出现异常。异常发生在第{0}列上，其指定列ID为{1}，实际ID为{2}，值为{3}。", i, lstValue[i].ID, dr.GetName(i), dr[i].ToString());
                    throw;
                }
            }

            return curStream.Position - len;

        }

        #endregion

        #region 打包命令

        /// <summary>
        /// 为数据包创建命令节
        /// </summary>
        /// <param name="name">命令名称</param>
        /// <param name="options">操作类型</param>
        /// <returns>命令节的流</returns>
        public override SectionStream CreatCommand(string name, UInt32 options)
       {
            if (DataPackageMan != null)
            {
                this.PakState = PackageState.CreatCommand;
                return _cmdStream = DataPackageMan.CreatCommand(name, options);
            }

            return null;
        }

        public override bool CloseCommandStream()
        {
            if (_cmdStream != null)
            {
                _cmdStream.Close();
                _cmdStream = null;

                return true;
            }

            return false;
        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="lstValue"></param>
        /// <param name="isStopPending"></param>
        /// <returns></returns>
        public override List<string> WriteData(SqlCommand cmd, List<CommandValue> lstValue, IsStopPending isStopPending)
        {
            if (this.PakState != PackageState.GetDataTarget)
            {
                LogMan.ErrorFormat("警告：无法为包添加数据，当前一个标记并不是添加数据标记。SQL语句为：{0}", cmd.CommandText);
                return null;
            }
            //保存分包后临时数据保存的文件名集合，没有分包时为null
            //保留超限数据文件保存的位置
            List<string> lstFile = WriteDataInDataSection(cmd, lstValue, isStopPending);
            //如果已经生成数据存储临时文件，关闭原流。将流指针指向新文件
            if (lstFile != null)
            {
                //保存临时文件名集合到全局变量
                if (LstSplitPakFiles == null || LstSplitPakFiles.Count == 0)
                {
                    LstSplitPakFiles = new List<string>();
                    //保留DataHeader中的数据
                    bufferForCmdDataHeader = new byte[lPosHeader];
                    _dataStream.Seek(0, SeekOrigin.Begin);
                    _dataStream.Read(bufferForCmdDataHeader, 0, bufferForCmdDataHeader.Length);

                    //保留该命令到Data节前的固定数据
                    bufferForCmd = new byte[lPos];
                    lPos = _cmdStream.Position;
                    _cmdStream.Seek(0, SeekOrigin.Begin);
                    _cmdStream.Read(bufferForCmd, 0, bufferForCmd.Length);
                    //恢复Command节的当前位置
                    _cmdStream.Position = lPos;
                }
                foreach (string sFile in lstFile)
                {
                    LstSplitPakFiles.Add(sFile);
                }
                _dataStream.Close();
                //如果有多个数据存储临时文件，将dataStream指向最后一个文件的末尾（新读取的数据从此添加）
                _dataStream = new FileStream(lstFile[lstFile.Count - 1], FileMode.Open, FileAccess.ReadWrite);
                _dataStream.Seek(0, SeekOrigin.End);
            }
            this.PakState = PackageState.GetData;

            return LstSplitPakFiles;

        }
        /// <summary>
        /// 添加用户命令
        /// </summary>
        /// <param name="ndTypeValue"></param>
        /// <param name="ReplaceParameters"></param>
        /// <param name="ReserveText"></param>
        public override void AddTargetCommands(string ndTypeValue, string ndText, string ndReserveText)
        {
            if (this.PakState != PackageState.CreatCommand &&
                this.PakState != PackageState.GetUserTargetCommand &&
                this.PakState != PackageState.GetData)
            {
                LogMan.ErrorFormat("警告：无法为包添加命令，当前一个标记并没有创建命令节标记。Text语句为：{0}", ndText);
                return;
            }
            SectionStream scStream = null;
            SectionStream nestedStream = null;
            //创建目标命令节
            using (scStream = SectionStream.Create(_cmdStream, ndTypeValue, 0, LogMan))
            {
                //创建嵌套的SQL命令节
                nestedStream = SectionStream.Create(scStream, "SQL", 0, LogMan);

                WriteString(nestedStream, ndText);
                if (ndReserveText != null && ndReserveText != string.Empty)
                {
                    WriteString(nestedStream, ndReserveText);
                }
                nestedStream.Close();
                scStream.Close();
            }

            this.PakState = PackageState.GetUserTargetCommand;
        }

        /// <summary>
        /// 获取客户端命令、参数，写入包中。
        /// </summary>
        /// <param name="ndTypeValue"></param>
        /// <param name="ndText"></param>
        /// <param name="ndReserveText"></param>
        /// <param name="lstInfo"></param>
        public override void AddTargetCommands(string ndTypeValue, string ndText, string ndReserveText, List<ParameterInfo> lstInfo, List<CommandValue> lstValue)
        {
            if (this.PakState != PackageState.CreatCommand &&
                this.PakState != PackageState.GetUserTargetCommand &&
                this.PakState != PackageState.GetDataTarget &&
                this.PakState != PackageState.GetData)
            {
                LogMan.ErrorFormat("警告：无法为包添加命令，当前一个标记并没有创建命令节标记。Text语句为：{0}", ndText);
                return;
            }
            if (_dataStream != null)
            { return; }
            SectionStream scStream = null;
            SectionStream nestedStream = null;
            BinaryFormatter bf = new BinaryFormatter();
            //创建目标命令节
            using (scStream = SectionStream.Create(_cmdStream, ndTypeValue, 0, LogMan))
            {
                //创建嵌套的SQL命令节
                nestedStream = SectionStream.Create(scStream, "SQL", 0, LogMan);
                WriteString(nestedStream, ndText);
                if (ndReserveText != null && ndReserveText != string.Empty)
                {
                    WriteString(nestedStream, ndReserveText);
                }
                nestedStream.Close();
                //创建嵌套的参数节
                nestedStream = SectionStream.Create(scStream, "Parameter", 0, LogMan);

                bf.Serialize(nestedStream, lstInfo);

                nestedStream.Close();
                scStream.Close();
            }
            //保留Command固定数据（命令、参数）写入的位置。
            lPos = _cmdStream.Position;

            _dataStream = CreateDataSec(lstValue);

            lPosHeader = _dataStream.Position;

            this.PakState = PackageState.GetDataTarget;

        }

        public override void DeleteStreamFile()
        {
            if (_pakStream != null)
            {
                _pakStream.Close();
                if (_pakStream.GetType() == typeof(FileStream))
                {
                    File.Delete(((FileStream)_pakStream).Name);
                }
            }

        }
        /// <summary>
        /// 清除切割文件
        /// </summary>
        public override void ClearSpliteFiles()
        {
            if (LstSplitPakFiles != null)
            {
                foreach (string sfile in LstSplitPakFiles)
                {
                    File.Delete(sfile);
                }
                LstSplitPakFiles.Clear();
            }
        }
        #endregion

    }


}
